package eval

import (
	"showen/lex"
	"showen/obj"
	"showen/parse"
	"testing"
)

func TestIOProcedures(t *testing.T) {
	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			name:     "open-input-file",
			input:    "(open-input-file \"test.txt\")",
			expected: "#<input port>",
		},
		{
			name:     "open-output-file",
			input:    "(open-output-file \"test.txt\")",
			expected: "#<output port>",
		},
		{
			name:     "eof-object?",
			input:    "(eof-object? 1)",
			expected: "#f",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			lexer := lex.NewLexer(tt.input)
			tokens := lexer.ReadTokens()
			parser := parse.NewParser(tokens)
			expr, err := parser.ParseExpression()
			if err != nil {
				t.Fatalf("ParseExpression() error = %v", err)
			}

			result, err := EvalExpression(expr, env)
			if err != nil {
				t.Fatalf("EvalExpression() error = %v", err)
			}

			if result.Inspect() != tt.expected {
				t.Errorf("EvalExpression() = %v, want %v", result.Inspect(), tt.expected)
			}
		})
	}
}

func TestIOErrorHandling(t *testing.T) {
	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			name:     "open-input-file with no arguments",
			input:    "(open-input-file)",
			expected: "open-input-file requires exactly 1 argument",
		},
		{
			name:     "open-input-file with non-string argument",
			input:    "(open-input-file 1)",
			expected: "open-input-file argument must be a string",
		},
		{
			name:     "open-output-file with no arguments",
			input:    "(open-output-file)",
			expected: "open-output-file requires exactly 1 argument",
		},
		{
			name:     "open-output-file with non-string argument",
			input:    "(open-output-file 1)",
			expected: "open-output-file argument must be a string",
		},
		{
			name:     "close-input-port with no arguments",
			input:    "(close-input-port)",
			expected: "close-input-port requires exactly 1 argument",
		},
		{
			name:     "close-input-port with non-port argument",
			input:    "(close-input-port 1)",
			expected: "close-input-port argument must be an input port",
		},
		{
			name:     "close-output-port with no arguments",
			input:    "(close-output-port)",
			expected: "close-output-port requires exactly 1 argument",
		},
		{
			name:     "close-output-port with non-port argument",
			input:    "(close-output-port 1)",
			expected: "close-output-port argument must be an output port",
		},
		{
			name:     "eof-object? with no arguments",
			input:    "(eof-object?)",
			expected: "eof-object? requires exactly 1 argument",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			lexer := lex.NewLexer(tt.input)
			tokens := lexer.ReadTokens()
			parser := parse.NewParser(tokens)
			expr, err := parser.ParseExpression()
			if err != nil {
				t.Fatalf("ParseExpression() error = %v", err)
			}

			result, err := EvalExpression(expr, env)
			if err != nil {
				// Evaluation error - this is what we expect
				if err.Error() != tt.expected {
					t.Errorf("For input %q, expected error %q, got %q", tt.input, tt.expected, err.Error())
				}
				return
			}

			// Check if the result is an error object
			if result.Type() != obj.ERROR_OBJ {
				t.Fatalf("Expected error result, got %v", result.Type())
			}

			errorResult := result.(*Error)
			if errorResult.Message != tt.expected {
				t.Errorf("For input %q, expected error %q, got %q", tt.input, tt.expected, errorResult.Message)
			}
		})
	}
}
